using System;
using PluginScheduler.Interfaces;

namespace PluginScheduler.Jobs
{
    public class JobBuilder
    {
        private JobKey _key;
        private string _description;
        private Type _jobType;

        private JobDataMap jobDataMap = new JobDataMap();

        private JobBuilder()
        {
        }

        /// <summary>
        /// Create a JobBuilder with which to define a <code>JobDetail</code>.
        /// </summary>
        /// <returns>a new JobBuilder</returns>
        public static JobBuilder Create()
        {
            return new JobBuilder();
        }

        /// <summary>
        /// Create a JobBuilder with which to define a <code>JobDetail</code>,
        /// and set the class name of the <code>Job</code> to be executed.
        /// </summary>
        /// <returns>a new JobBuilder</returns>
        public static JobBuilder Create(Type jobType)
        {
            var b = new JobBuilder();
            b.OfType(jobType);
            return b;
        }

        /// <summary>
        /// Create a JobBuilder with which to define a <code>JobDetail</code>,
        /// and set the class name of the <code>Job</code> to be executed.
        /// </summary>
        /// <returns>a new JobBuilder</returns>
        public static JobBuilder Create<T>() where T : IJob
        {
            var b = new JobBuilder();
            b.OfType(typeof(T));
            return b;
        }

        /// <summary>
        /// Produce the <code>JobDetail</code> instance defined by this 
        /// <code>JobBuilder</code>.
        /// </summary>
        /// <returns>the defined JobDetail.</returns>
        public IJobDetail Build()
        {
            var job = new JobDetailImpl
                          {
                              JobType = _jobType, 
                              Description = _description
                          };

            if (_key == null)
            {
                _key = new JobKey(Guid.NewGuid().ToString(), null);
            }
            job.Key = _key;

            if (jobDataMap.Count != 0)
            {
                job.JobDataMap = jobDataMap;
            }

            return job;
        }

        public JobBuilder WithIdentity(string name)
        {
            _key = new JobKey(name, null);
            return this;
        }

        public JobBuilder WithIdentity(JobKey key)
        {
            this._key = key;
            return this;
        }

        /// <summary>
        /// Set the given (human-meaningful) description of the Job.
        /// </summary>
        /// <param name="description"> the description for the Job</param>
        /// <returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.Description" />
        public JobBuilder WithDescription(string description)
        {
            this._description = description;
            return this;
        }

        /// <summary>
        /// Set the class which will be instantiated and executed when a
        /// Trigger fires that is associated with this JobDetail.
        /// </summary>
        /// <returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobType" />
        public JobBuilder OfType<T>()
        {
            return OfType(typeof(T));
        }

        /// <summary>
        /// Set the class which will be instantiated and executed when a
        /// Trigger fires that is associated with this JobDetail.
        /// </summary>
        /// <returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobType" />
        public JobBuilder OfType(Type type)
        {
            _jobType = type;
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, string value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, int value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, long value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, float value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, double value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Add the given key-value pair to the JobDetail's <see cref="JobDataMap" />.
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(string key, bool value)
        {
            jobDataMap.Put(key, value);
            return this;
        }

        /// <summary>
        /// Set the JobDetail's <see cref="JobDataMap" />, adding any values to it
        /// that were already set on this JobBuilder using any of the
        /// other 'usingJobData' methods. 
        /// </summary>
        ///<returns>the updated JobBuilder</returns>
        /// <seealso cref="IJobDetail.JobDataMap" />
        public JobBuilder UsingJobData(JobDataMap newJobDataMap)
        {
            // add any existing data to this new map
            foreach (var key in jobDataMap.Keys)
            {
                newJobDataMap.Put(key, jobDataMap.Get(key));
            }
            jobDataMap = newJobDataMap; // set new map as the map to use
            return this;
        }
    }
}